1 /*
2 * $Header: /home/cvs/jakarta-commons/betwixt/src/java/org/apache/commons/betwixt/io/AbstractBeanWriter.java,v 1.6 2002/10/14 01:55:38 dion Exp $
3 * $Revision: 1.6 $
4 * $Date: 2002/10/14 01:55:38 $
5 *
6 * ====================================================================
7 *
8 * The Apache Software License, Version 1.1
9 *
10 * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
11 * reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 *
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in
22 * the documentation and/or other materials provided with the
23 * distribution.
24 *
25 * 3. The end-user documentation included with the redistribution, if
26 * any, must include the following acknowlegement:
27 * "This product includes software developed by the
28 * Apache Software Foundation (http://www.apache.org/)."
29 * Alternately, this acknowlegement may appear in the software itself,
30 * if and wherever such third-party acknowlegements normally appear.
31 *
32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
33 * Foundation" must not be used to endorse or promote products derived
34 * from this software without prior written permission. For written
35 * permission, please contact apache@apache.org.
36 *
37 * 5. Products derived from this software may not be called "Apache"
38 * nor may "Apache" appear in their names without prior written
39 * permission of the Apache Group.
40 *
41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52 * SUCH DAMAGE.
53 * ====================================================================
54 *
55 * This software consists of voluntary contributions made by many
56 * individuals on behalf of the Apache Software Foundation. For more
57 * information on the Apache Software Foundation, please see
58 * <http://www.apache.org/>.
59 *
60 * $Id: AbstractBeanWriter.java,v 1.6 2002/10/14 01:55:38 dion Exp $
61 */
62 package org.apache.commons.betwixt.io;
63
64 import java.beans.IntrospectionException;
65 import java.io.BufferedWriter;
66 import java.io.IOException;
67 import java.io.OutputStream;
68 import java.io.OutputStreamWriter;
69 import java.io.Writer;
70 import java.util.Iterator;
71 import java.util.HashMap;
72
73 import org.apache.commons.logging.Log;
74 import org.apache.commons.logging.LogFactory;
75
76 import org.apache.commons.betwixt.AttributeDescriptor;
77 import org.apache.commons.betwixt.ElementDescriptor;
78 import org.apache.commons.betwixt.XMLBeanInfo;
79 import org.apache.commons.betwixt.XMLIntrospector;
80 import org.apache.commons.betwixt.expression.Context;
81 import org.apache.commons.betwixt.expression.Expression;
82 import org.apache.commons.betwixt.io.id.SequentialIDGenerator;
83
84 import org.xml.sax.SAXException;
85
86 // FIX ME!!!
87 // Logging logic!
88
89 // FIX ME!!
90 // Error handling strategy!
91 // i'm going to add SAXExceptions everywhere since it's the easiest way to make things work quick
92 // but this is a poor strategy
93
94 /***
95 * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a>
96 * @version $Revision: 1.6 $
97 */
98 abstract public class AbstractBeanWriter {
99
100 /*** Introspector used */
101 protected XMLIntrospector introspector = new XMLIntrospector();
102
103 /*** Log used for logging (Doh!) */
104 private Log log = LogFactory.getLog( AbstractBeanWriter.class );
105 /*** Map containing ID attribute values for beans */
106 protected HashMap idMap = new HashMap();
107 /*** Used to generate ID attribute values*/
108 protected IDGenerator idGenerator = new SequentialIDGenerator();
109 /*** Should generated <code>ID</code> attribute values be added to the elements? */
110 protected boolean writeIDs = true;
111
112 /*** indentation level */
113 protected int indentLevel;
114
115 /***
116 * <p> Writes the given bean to the current stream using the XML introspector.</p>
117 *
118 * <p> This writes an xml fragment representing the bean to the current stream.</p>
119 *
120 * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
121 * is encountered in the graph <strong>only</strong> if the <code>WriteIDs</code>
122 * property is false.</p>
123 *
124 * @throws CyclicReferenceException when a cyclic reference is encountered
125 *
126 * @param bean write out representation of this bean
127 */
128 public void write(Object bean) throws IOException, SAXException, IntrospectionException {
129 if (log.isDebugEnabled()) {
130 log.debug( "Writing bean graph..." );
131 log.debug( bean );
132 }
133 start();
134 write( null, bean );
135 end();
136 if (log.isDebugEnabled()) {
137 log.debug( "Finished writing bean graph." );
138 }
139 }
140
141 /***
142 * Marks the start of the bean writing.
143 * By default doesn't do anything, but can be used
144 * to do extra start processing
145 * @throws IOException
146 * @throws SAXException
147 */
148 public void start() throws IOException, SAXException {
149 }
150
151 /***
152 * Marks the start of the bean writing.
153 * By default doesn't do anything, but can be used
154 * to do extra end processing
155 * @throws IOExcpetion
156 * @throws SAXException
157 */
158
159 public void end() throws IOException, SAXException {
160 }
161
162
163
164 /***
165 * <p>Writes the given bean to the current stream using the given <code>qualifiedName</code>.</p>
166 *
167 * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
168 * is encountered in the graph <strong>only</strong> if the <code>WriteIDs</code>
169 * property is false.</p>
170 *
171 * @throws CyclicReferenceException when a cyclic reference is encountered
172 */
173 public void write(
174 String qualifiedName,
175 Object bean)
176 throws
177 IOException,
178 SAXException,
179 IntrospectionException {
180
181
182 if ( log.isTraceEnabled() ) {
183 log.trace( "Writing bean graph (qualified name '" + qualifiedName + "'" );
184 }
185
186 // introspect to obtain bean info
187 XMLBeanInfo beanInfo = introspector.introspect( bean );
188 if ( beanInfo != null ) {
189 ElementDescriptor elementDescriptor = beanInfo.getElementDescriptor();
190 if ( elementDescriptor != null ) {
191 Context context = new Context( bean, log );
192 if ( qualifiedName == null ) {
193 qualifiedName = elementDescriptor.getQualifiedName();
194 }
195
196 String ref = null;
197 String id = null;
198
199 // only give id's to non-primatives
200 if ( elementDescriptor.isPrimitiveType() ) {
201 // write without an id
202 write(
203 qualifiedName,
204 elementDescriptor,
205 context );
206 }
207 else {
208
209 ref = (String) idMap.get( context.getBean() );
210 if ( ref == null ) {
211 // this is the first time that this bean has be written
212 AttributeDescriptor idAttribute = beanInfo.getIDAttribute();
213 if (idAttribute == null) {
214 // use a generated id
215 id = idGenerator.nextId();
216 idMap.put( bean, id );
217
218 if ( writeIDs ) {
219 // write element with id
220 write(
221 qualifiedName,
222 elementDescriptor,
223 context ,
224 beanInfo.getIDAttributeName(),
225 id);
226
227 } else {
228 // write element without ID
229 write(
230 qualifiedName,
231 elementDescriptor,
232 context );
233 }
234
235 } else {
236 // use id from bean property
237 // it's up to the user to ensure uniqueness
238 // XXX should we trap nulls?
239 Object exp = idAttribute.getTextExpression().evaluate( context );
240 if (exp == null) {
241 // we'll use a random id
242 log.debug("Using random id");
243 id = idGenerator.nextId();
244
245 } else {
246 // convert to string
247 id = exp.toString();
248 }
249 idMap.put( bean, id);
250
251 // the ID attribute should be written automatically
252 write(
253 qualifiedName,
254 elementDescriptor,
255 context );
256 }
257 }
258 else {
259 // we have a cyclic reference
260 if ( !writeIDs ) {
261 // if we're not writing IDs, then throw exception
262 throw new CyclicReferenceException();
263 }
264
265 // we've already written this bean so write an IDREF
266 writeIDREFElement(
267 qualifiedName,
268 beanInfo.getIDREFAttributeName(),
269 ref);
270 }
271 }
272 }
273 }
274
275 log.trace( "Finished writing bean graph." );
276 }
277
278 /***
279 * Get <code>IDGenerator</code> implementation used to generate <code>ID</code> attribute values .
280 *
281 * @return implementation used for <code>ID</code> attribute generation
282 */
283 public IDGenerator getIdGenerator() {
284 return idGenerator;
285 }
286
287 /***
288 * Set <code>IDGenerator</code> implementation used to generate <code>ID</code> attribute values.
289 * This property can be used to customize the algorithm used for generation.
290 *
291 * @param idGenerator use this implementation for <code>ID</code> attribute generation
292 */
293 public void setIdGenerator(IDGenerator idGenerator) {
294 this.idGenerator = idGenerator;
295 }
296
297 /*** Get whether generated <code>ID</code> attribute values should be added to the elements */
298 public boolean getWriteIDs() {
299 return writeIDs;
300 }
301
302 /***
303 * Set whether generated <code>ID</code> attribute values should be added to the elements
304 * If this property is set to false, then <code>CyclicReferenceException</code>
305 * will be thrown whenever a cyclic occurs in the bean graph.
306 */
307 public void setWriteIDs(boolean writeIDs) {
308 this.writeIDs = writeIDs;
309 }
310
311 /***
312 * <p> Get the introspector used. </p>
313 *
314 * <p> The {@link XMLBeanInfo} used to map each bean is created by the <code>XMLIntrospector</code>.
315 * One way in which the mapping can be customized is by altering the <code>XMLIntrospector</code>. </p>
316 */
317 public XMLIntrospector getXMLIntrospector() {
318 return introspector;
319 }
320
321
322 /***
323 * <p> Set the introspector to be used. </p>
324 *
325 * <p> The {@link XMLBeanInfo} used to map each bean is created by the <code>XMLIntrospector</code>.
326 * One way in which the mapping can be customized is by altering the <code>XMLIntrospector</code>. </p>
327 *
328 * @param introspector use this introspector
329 */
330 public void setXMLIntrospector(XMLIntrospector introspector) {
331 this.introspector = introspector;
332 }
333
334 /***
335 * <p> Get the current level for logging. </p>
336 *
337 * @return a <code>org.apache.commons.logging.Log</code> level constant
338 */
339 public Log getLog() {
340 return log;
341 }
342
343 /***
344 * <p> Set the current logging level. </p>
345 *
346 * @param level a <code>org.apache.commons.logging.Log</code> level constant
347 */
348 public void setLog(Log log) {
349 this.log = log;
350 }
351
352
353 // Expression methods
354 //-------------------------------------------------------------------------
355
356 /*** Express an element tag start using given qualified name */
357 abstract protected void expressElementStart(String qualifiedName) throws IOException, SAXException;
358
359 abstract protected void expressTagClose() throws IOException, SAXException;
360
361 /*** Express an element end tag using given qualifiedName */
362 abstract protected void expressElementEnd(String qualifiedName) throws IOException, SAXException;
363
364 /*** Express an empty element end */
365 abstract protected void expressElementEnd() throws IOException, SAXException;
366
367 /*** Express body text */
368 abstract protected void expressBodyText(String text) throws IOException, SAXException;
369
370 /*** Express an attribute */
371 abstract protected void expressAttribute(
372 String qualifiedName,
373 String value)
374 throws
375 IOException,
376 SAXException;
377
378
379 // Implementation methods
380 //-------------------------------------------------------------------------
381
382
383 /*** Writes the given element */
384 protected void write(
385 String qualifiedName,
386 ElementDescriptor elementDescriptor,
387 Context context )
388 throws
389 IOException,
390 SAXException,
391 IntrospectionException {
392
393 if (elementDescriptor.isWrapCollectionsInElement()) {
394 expressElementStart( qualifiedName );
395 }
396
397 writeRestOfElement( qualifiedName, elementDescriptor, context);
398 }
399
400
401
402 /*** Writes the given element adding an ID attribute */
403 protected void write(
404 String qualifiedName,
405 ElementDescriptor elementDescriptor,
406 Context context,
407 String idAttribute,
408 String idValue )
409 throws
410 IOException,
411 SAXException,
412 IntrospectionException {
413
414 expressElementStart( qualifiedName );
415
416 expressAttribute( idAttribute, idValue );
417
418 writeRestOfElement( qualifiedName, elementDescriptor, context );
419 }
420
421 /*** Write attributes, child elements and element end */
422 protected void writeRestOfElement(
423 String qualifiedName,
424 ElementDescriptor elementDescriptor,
425 Context context )
426 throws
427 IOException,
428 SAXException,
429 IntrospectionException {
430
431 if (elementDescriptor.isWrapCollectionsInElement()) {
432 writeAttributes( elementDescriptor, context );
433 }
434
435 if ( writeContent( elementDescriptor, context ) ) {
436 if (elementDescriptor.isWrapCollectionsInElement()) {
437 expressElementEnd( qualifiedName );
438 }
439 }
440 else {
441 if (elementDescriptor.isWrapCollectionsInElement()) {
442 expressElementEnd();
443 }
444 }
445 }
446
447
448
449
450 protected void writeIDREFElement(
451 String qualifiedName,
452 String idrefAttributeName,
453 String idrefAttributeValue )
454 throws
455 IOException,
456 SAXException,
457 IntrospectionException {
458
459 // write IDREF element
460 expressElementStart( qualifiedName );
461
462 expressAttribute( idrefAttributeName, idrefAttributeValue );
463
464 expressElementEnd();
465 }
466
467 /*** Writes the element content.
468 *
469 * @return true if some content was written
470 */
471 protected boolean writeContent(
472 ElementDescriptor elementDescriptor,
473 Context context )
474 throws
475 IOException,
476 SAXException,
477 IntrospectionException {
478 ElementDescriptor[] childDescriptors = elementDescriptor.getElementDescriptors();
479 boolean writtenContent = false;
480 if ( childDescriptors != null && childDescriptors.length > 0 ) {
481 // process child elements
482 for ( int i = 0, size = childDescriptors.length; i < size; i++ ) {
483 ElementDescriptor childDescriptor = childDescriptors[i];
484 Context childContext = context;
485 Expression childExpression = childDescriptor.getContextExpression();
486 if ( childExpression != null ) {
487 Object childBean = childExpression.evaluate( context );
488 if ( childBean != null ) {
489 String qualifiedName = childDescriptor.getQualifiedName();
490 // XXXX: should we handle nulls better
491 if ( childBean instanceof Iterator ) {
492 for ( Iterator iter = (Iterator) childBean; iter.hasNext(); ) {
493 Object object = iter.next();
494 if (object == null) {
495 continue;
496 }
497 if ( ! writtenContent ) {
498 writtenContent = true;
499 if (elementDescriptor.isWrapCollectionsInElement()) {
500 expressTagClose();
501 }
502 }
503 ++indentLevel;
504 write( qualifiedName, object );
505 --indentLevel;
506 }
507 }
508 else {
509 if ( ! writtenContent ) {
510 writtenContent = true;
511 expressTagClose();
512 }
513 ++indentLevel;
514 write( qualifiedName, childBean );
515 --indentLevel;
516 }
517 }
518 }
519 else {
520 if ( ! writtenContent ) {
521 writtenContent = true;
522 expressTagClose();
523 }
524 if (childDescriptor.isWrapCollectionsInElement()) {
525 ++indentLevel;
526 }
527
528 write( childDescriptor.getQualifiedName(), childDescriptor, childContext );
529
530 if (childDescriptor.isWrapCollectionsInElement()) {
531 --indentLevel;
532 }
533 }
534 }
535 if ( writtenContent ) {
536 writePrintln();
537 writeIndent();
538 }
539 }
540 else {
541 // evaluate the body text
542 Expression expression = elementDescriptor.getTextExpression();
543 if ( expression != null ) {
544 Object value = expression.evaluate( context );
545 if ( value != null ) {
546 String text = value.toString();
547 if ( text != null && text.length() > 0 ) {
548 if ( ! writtenContent ) {
549 writtenContent = true;
550 expressTagClose();
551 }
552 expressBodyText(text);
553 }
554 }
555 }
556 }
557 return writtenContent;
558 }
559
560 /*** Writes the attribute declarations */
561 protected void writeAttributes(
562 ElementDescriptor elementDescriptor,
563 Context context )
564 throws
565 IOException, SAXException {
566 if (!elementDescriptor.isWrapCollectionsInElement())
567 return;
568
569 AttributeDescriptor[] attributeDescriptors = elementDescriptor.getAttributeDescriptors();
570 if ( attributeDescriptors != null ) {
571 for ( int i = 0, size = attributeDescriptors.length; i < size; i++ ) {
572 AttributeDescriptor attributeDescriptor = attributeDescriptors[i];
573 writeAttribute( attributeDescriptor, context );
574 }
575 }
576 }
577
578
579 /*** Writes an attribute declaration */
580 protected void writeAttribute(
581 AttributeDescriptor attributeDescriptor,
582 Context context )
583 throws
584 IOException, SAXException {
585 Expression expression = attributeDescriptor.getTextExpression();
586 if ( expression != null ) {
587 Object value = expression.evaluate( context );
588 if ( value != null ) {
589 String text = value.toString();
590 if ( text != null && text.length() > 0 ) {
591 expressAttribute(attributeDescriptor.getQualifiedName(), text);
592 }
593 }
594 }
595 }
596
597 protected void writePrintln() throws IOException {}
598 protected void writeIndent() throws IOException {}
599 }
This page was automatically generated by Maven